/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "Server.h"

#include <cstring>
#include <sys/stat.h>

#include "coap3/coap_resource.h"
#include "CoapResource.h"
#include "third_party_bounds_checking_function/include/securec.h"

static const int FIRST = 0;
static const int SECOND = 1;
static const int THIRD = 2;
static const int COUNT_ONE = 1;
static const int COUNT_TWO = 2;
static const int COUNT_THREE = 3;

// namespace Server {
struct AsyncCallbackInfo {
    napi_env env;
    napi_async_work asyncWork;
    napi_deferred deferred;
    Server *server;
};

unsigned int Server::GetRunning()
{
    return running;
}

void Server::SetRunning(unsigned int status)
{
    running = status;
}

static void ServerRun(Server *server, coap_context_t *ctx)
{
    int coapFd = coap_context_get_coap_fd(ctx);
    int nfds = 0;
    fd_set m_readfds;
    if (coapFd != -1) {
        /* if coapFd is -1, then epoll is not supported within libcoap */
        FD_ZERO(&m_readfds);
        FD_SET(coapFd, &m_readfds);
        nfds = coapFd + 1;
    }

    unsigned waitMs = COAP_RESOURCE_CHECK_TIME * ONE_SECOND;
    while (server->GetRunning() == RUNNING) {
        int result;
        if (coapFd != -1) {
            fd_set readfds = m_readfds;
            struct timeval tv;
            coap_tick_t begin;
            coap_tick_t end;
            coap_ticks(&begin);

            tv.tv_sec = waitMs / ONE_SECOND;
            tv.tv_usec = (waitMs % ONE_SECOND) * ONE_SECOND;
            result = select(nfds, &readfds, NULL, NULL, &tv);
            if (result == -1 && errno != EAGAIN) {
                coap_log_debug("select: %s (%d)\n", coap_socket_strerror(), errno);
                break;
            }
            if (result > 0 && FD_ISSET(coapFd, &readfds)) {
                result = coap_io_process(ctx, COAP_IO_NO_WAIT);
            }
            if (result >= 0) {
                coap_ticks(&end);
                /* Track the overall time spent in select() and coap_io_process() */
                result = (int)(end - begin);
            }
        } else {
            result = coap_io_process(ctx, waitMs);
        }
        if (result < 0) {
            break;
        } else if (result && static_cast<unsigned>(result) < waitMs) {
            waitMs -= result;
        } else {
            waitMs = COAP_RESOURCE_CHECK_TIME * ONE_SECOND;
        }
    }
}

static void finish(coap_context_t *ctx, Server *server)
{
    coap_free_context(ctx);
    coap_cleanup();
    server->resources.clear();
    server->SetRunning(STOP);
}

static void ServerNative(Server *server)
{
    coap_context_t *ctx = nullptr;
    int result = EXIT_FAILURE;

    uint32_t scheme_hint_bits;
    coap_addr_info_t *info = nullptr;
    coap_addr_info_t *info_list = nullptr;

    LOGI("ServerNative ip_address = %s, port = %s", server->ip, server->port);
    coap_str_const_t *my_address = coap_make_str_const(server->ip);
    bool haveEp = false;

    /* Initialize libcoap library */
    coap_startup();
    coap_set_log_level(COAP_LOG_WARN);
    /* Create coAP context */
    ctx = coap_new_context(nullptr);
    if (!ctx) {
        coap_log_emerg("cannot initialize context\n");
        return finish(ctx, server);
    }
 
    scheme_hint_bits = coap_get_available_scheme_hint_bits(0, 0, COAP_PROTO_NONE);
    info_list = coap_resolve_address_info(my_address, std::stoi(std::string(server->port)),
        0, 0, 0, 0, scheme_hint_bits, COAP_RESOLVE_TYPE_LOCAL);
    /* Create coAP listening endpoint(s) */
    for (info = info_list; info != NULL; info = info->next) {
        coap_endpoint_t *ep;

        ep = coap_new_endpoint(ctx, &info->addr, info->proto);
        if (!ep) {
            coap_log_warn("cannot create endpoint for CoAP proto %u\n", info->proto);
        } else {
            haveEp = true;
        }
    }
    coap_free_address_info(info_list);
    if (!haveEp) {
        coap_log_err("No context available for interface '%s'\n", (const char *)my_address->s);
        return finish(ctx, server);
    }
    /* Add in Multicast listening as appropriate */
    coap_join_mcast_group_intf(ctx, server->ip, NULL);
    for (auto i : server->resources) {
        coap_add_resource(ctx, i);
    }
    server->SetRunning(RUNNING);
    
    /* Handle any libcoap I/O requirements */
    ServerRun(server, ctx);
    result = EXIT_SUCCESS;

    return finish(ctx, server);
}

static void DealCallBack(napi_env env, AsyncCallbackInfo *asyncCallbackInfo)
{
    napi_value arr;
    napi_create_array(env, &arr);

    napi_value code;
    napi_create_int32(env, 1, &code);
    napi_value obj;
    napi_create_object(env, &obj);
    napi_set_named_property(env, obj, "code", code);
    napi_set_named_property(env, obj, "message", arr);
    napi_resolve_deferred(asyncCallbackInfo->env, asyncCallbackInfo->deferred, obj);
    napi_delete_async_work(env, asyncCallbackInfo->asyncWork);
    delete asyncCallbackInfo;
    asyncCallbackInfo = nullptr;
}

static std::unordered_map<std::string, Server *> g_serverMap;

static Server *getServer(std::string classIdStr)
{
    Server *server = nullptr;
    auto iter = g_serverMap.find(classIdStr);
    if (iter != g_serverMap.end()) {
        server = g_serverMap[classIdStr];
    }
    return server;
}

// 定义线程数据结构体
struct ThreadSafeInfo {
    char *sum;
};

Server::Server()
{
    running = STOP;
    if (memcpy_s(ip, sizeof(ip), "127.0.0.1", sizeof("127.0.0.1")) != 0) {
        LOGI("memcpy_s ip failed");
    }
    if (memcpy_s(port, sizeof(port), "0", sizeof("0")) != 0) {
        LOGI("memcpy_s ip failed");
    }
}

napi_value Server::GetStatus(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_status status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < COUNT_ONE) {
        napi_throw_type_error(env, nullptr, "Expected at least 1 argument for GetStatus.");
        return nullptr;
    }

    // 获取当前唯一标识classId
    size_t len = DEFAULT_BUFF_SIZE;
    char classId[DEFAULT_BUFF_SIZE] = {0};
    napi_get_value_string_utf8(env, args[FIRST], classId, len, &len);
    std::string classIdStr = classId;
    Server *server = getServer(classIdStr);
    if (!server) {
        return nullptr;
    }
    
    napi_value ret;
    napi_create_int32(env, server->GetRunning(), &ret);
    return ret;
}

napi_value Server::Start(napi_env env, napi_callback_info info)
{
    LOGI("ServerNative Server::Start");
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_status status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < COUNT_ONE) {
        napi_throw_type_error(env, nullptr, "Expected at least 1 argument for Start.");
        return nullptr;
    }

    // 获取当前唯一标识classId
    size_t len = DEFAULT_BUFF_SIZE;
    char classId[DEFAULT_BUFF_SIZE] = {0};
    napi_get_value_string_utf8(env, args[FIRST], classId, len, &len);
    std::string classIdStr = classId;
    Server *server = getServer(classIdStr);
    if (!server) {
        return nullptr;
    }

    napi_deferred deferred;
    napi_value promise;
    napi_create_promise(env, &deferred, &promise);
    AsyncCallbackInfo *asyncCallbackInfo =
        new AsyncCallbackInfo{.env = env, .asyncWork = nullptr, .deferred = deferred, .server = server};
    napi_value resourceName;
    napi_create_string_latin1(env, "ServerCreate", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(
        env, nullptr, resourceName,
        [](napi_env env, void *data) {
            AsyncCallbackInfo *asyncCallbackInfo = (AsyncCallbackInfo *)data;
            ServerNative(asyncCallbackInfo->server);
        },
        [](napi_env env, napi_status status, void *data) { DealCallBack(env, (AsyncCallbackInfo *)data); },
        (void *)asyncCallbackInfo, &asyncCallbackInfo->asyncWork);
    napi_queue_async_work(env, asyncCallbackInfo->asyncWork);
    return promise;
}

napi_value Server::Stop(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_status status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < COUNT_ONE) {
        napi_throw_type_error(env, nullptr, "Expected at least 1 argument for Stop.");
        return nullptr;
    }

    // 获取当前唯一标识classId
    size_t len = DEFAULT_BUFF_SIZE;
    char classId[DEFAULT_BUFF_SIZE] = {0};
    napi_get_value_string_utf8(env, args[FIRST], classId, len, &len);
    std::string classIdStr = classId;
    Server *server = getServer(classIdStr);
    if (!server) {
        return nullptr;
    }

    server->SetRunning(WAITSTOP);
    return nullptr;
}

napi_value Server::SetAddr(napi_env env, napi_callback_info info)
{
    size_t argc = 3;
    napi_value args[3];
    napi_status status;

    status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < COUNT_THREE) {
        napi_throw_type_error(env, nullptr, "Expected at least 3 argument for SetAddr.");
        return nullptr;
    }

    size_t charLen = DEFAULT_BUFF_SIZE;
    char classId[DEFAULT_BUFF_SIZE] = {0};
    napi_get_value_string_utf8(env, args[FIRST], classId, charLen, &charLen);
    Server *server = getServer(classId);
    if (!server) {
        return nullptr;
    }

    charLen = sizeof(server->ip) - 1;
    status = napi_get_value_string_utf8(env, args[SECOND], server->ip, charLen, &charLen);
    if (status != napi_ok) {
        napi_throw_type_error(env, nullptr, "Failed to convert argument to UTF-8 string.");
        return nullptr;
    }

    charLen = sizeof(server->port) - 1;
    status = napi_get_value_string_utf8(env, args[THIRD], server->port, charLen, &charLen);
    if (status != napi_ok) {
        napi_throw_type_error(env, nullptr, "Failed to convert argument to UTF-8 string.");
        return nullptr;
    }
    return nullptr;
}

napi_value Server::AddResource(napi_env env, napi_callback_info info)
{
    size_t argc = 2;
    napi_value args[2];
    napi_status status;

    status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < COUNT_TWO) {
        napi_throw_type_error(env, nullptr, "Expected at least 2 argument for AddResource.");
        return nullptr;
    }

    size_t charLen = DEFAULT_BUFF_SIZE;
    char classId[DEFAULT_BUFF_SIZE] = {0};
    napi_get_value_string_utf8(env, args[FIRST], classId, charLen, &charLen);
    Server *server = getServer(classId);
    if (!server) {
        return nullptr;
    }

    char resourceId[DEFAULT_BUFF_SIZE] = {0};
    charLen = sizeof(resourceId) - 1;  // 确保有足够的空间存放字符串和终止符
    status = napi_get_value_string_utf8(env, args[SECOND], resourceId, charLen, &charLen);
    if (status != napi_ok) {
        napi_throw_type_error(env, nullptr, "Failed to convert argument to UTF-8 string.");
        return nullptr;
    }

    std::string resourceIdStr(resourceId, charLen);  // 使用正确的长度构造字符串
    coap_resource_t *resource = CoapResource::getResource(resourceIdStr);
    if (!resource) {
        napi_throw_error(env, nullptr, "Resource not found.");
        return nullptr;
    }
    server->resources.push_back(resource);
    return nullptr;
}

napi_value Server::JsConstructor(napi_env env, napi_callback_info info)
{
    napi_value targetObj = nullptr;
    void *data = nullptr;
    size_t argsNum = 0;
    napi_value args[1] = {nullptr};
    napi_get_cb_info(env, info, &argsNum, args, &targetObj, &data);
    Server *classBind = new Server();
    uintptr_t classId = reinterpret_cast<uintptr_t>(classBind);
    std::string classIdStrTemp = std::to_string(classId);
    classBind->classIdStr = classIdStrTemp;
    napi_value napiClassId;
    srand(atoi(classIdStrTemp.c_str()));
    napi_create_string_utf8(env, classIdStrTemp.c_str(), classIdStrTemp.length(), &napiClassId);
    napi_set_named_property(env, targetObj, "classId", napiClassId);
    g_serverMap.insert(std::pair<std::string, Server *>(classIdStrTemp, classBind));

    napi_wrap(
        env, nullptr, classBind,
        [](napi_env env, void *data, void *hint) {
            Server *bind = (Server *)data;
            delete bind;
            bind = nullptr;
        },
        nullptr, nullptr);
    return targetObj;
}